import { JSDOM, ConstructorOptions } from 'jsdom';

const { beforeEach, afterEach } = globalThis || global || {};

export type InjectOptions = {
  html: string | Buffer,
} & ConstructorOptions;

export type InjectCallback = {
  beforeInject?: (opts: InjectOptions) => void,
  afterInject?: (dom: JSDOM, opts: InjectOptions) => void,
}

export const inject = (opts?: string | Buffer | (InjectOptions & InjectCallback)): void => {
  if (typeof beforeEach !== 'function' || typeof afterEach !== 'function') return;

  beforeEach(() => doInject(opts));
  afterEach(() => deInject());
};

export const doInject = (opts?: string | Buffer | (InjectOptions & InjectCallback)): void => {
  const { beforeInject, afterInject, ...rest } = filterInjectOptions(opts);
  typeof beforeInject === 'function' && beforeInject(rest);

  const dom = new JSDOM(rest.html, rest);
  if (globalThis != null) {
    // @ts-ignore DOMWindow type check
    globalThis.window = dom.window;
    globalThis.document = dom.window.document;
  } else if (global != null) {
    // @ts-ignore DOMWindow type check
    global['window'] = dom.window;
    global['document'] = dom.window.document;
  }

  typeof afterInject === 'function' && afterInject(dom, rest);
};

export const deInject = (): void => {
  if (globalThis != null) {
    // @ts-ignore operator must be optional.
    delete globalThis.window;
    // @ts-ignore operator must be optional.
    delete globalThis.document;
  } else if (global != null) {
    // @ts-ignore operator must be optional.
    delete global['window'];
    // @ts-ignore operator must be optional.
    delete global['document'];
  }
};

export const filterInjectOptions = (opts?: string | Buffer | (InjectOptions & InjectCallback) | null): InjectOptions & InjectCallback => {
  if (opts == null) {
    return { html: '<html lang="en"><body></body></html>' };
  }
  if (typeof opts === 'string' || Buffer.isBuffer(opts)) {
    return { html: opts };
  }
  return opts;
};
